#include "RUDP.h"
#ifdef _WIN32
#define INVALID_SOCKET  (SOCKET)(~0)
#include <ws2tcpip.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#else
#define INVALID_SOCKET  (int)(~0)
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdarg.h>
#endif
#define LOG_BUF_SIZE			1024
static timeval zerots;


#define SOCKET_ERROR            (-1)

RUDP::RUDP()
{
	static bool isInit = false;
	if (!isInit)
	{
		isInit = true;
#if _WIN32
		WSAData wd;
		if (WSAStartup(MAKEWORD(2, 2), &wd))
		{
			ErrorLog("WSAStartup error:%d", GetLastError());
			WSACleanup();
			return;
		}
#endif
	}

	memset(&zerots, 0, sizeof(timeval));
	rctp = new RCTP();
	rctp->RegisterRecvCallback(&RUDP::OnRecvMsg, this);
	rctp->RegisterSendCallback(&RUDP::OnSendImp, this);
	rctp->RegisterSendImpCallback(&RUDP::SendToImp, this);
	rctp->RegisterErrorLogCallback(&RUDP::ErrorLog, this);
}

RUDP::~RUDP()
{
	Terminate();
	delete rctp;
}

bool RUDP::Initialize(const char* ip, uint16_t port)
{
	Terminate();

	udpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (udpSocket == INVALID_SOCKET)
	{
		ErrorLog("socket error:%d",
#if _WIN32
			GetLastError()
#else
			errno
#endif
		);
		return false;
	}

	sockaddr_in addr;
	addr.sin_port = htons(port);
	//addr.sin_addr.s_addr = INADDR_ANY;
	mreq = new ip_mreq();
	mreq->imr_interface.s_addr = addr.sin_addr.s_addr = inet_addr(ip);
	addr.sin_family = AF_INET;
	int len = sizeof(sockaddr);
	if (bind(udpSocket, (sockaddr*)&addr, len) == SOCKET_ERROR)
	{
		ErrorLog("bind error:%d",
#if _WIN32
			GetLastError()
#else
			errno
#endif
		);
		return false;
	}
	int enable = 1;
	int ret = setsockopt(udpSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&enable, sizeof(enable));
	if (ret) {
		ErrorLog("setsockopt broadcast error");
		return false;
	}
#ifdef _WIN32
	int64_t timeout = 1;
	ret = setsockopt(udpSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(int64_t));
#else
	timeval tv = { 0, 1000 };
	ret = setsockopt(udpSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(timeval));
#endif

	if (ret) {
		ErrorLog("setsockopt rcvtimeo error");
		return false;
	}
	rctp->Initialize();
	return true;
}

void RUDP::Terminate()
{
	if (udpSocket != 0)
	{
#if _WIN32
		closesocket(udpSocket);
#else
		close(udpSocket);
#endif
	}
	udpSocket = 0;
	if (mreq)
		delete mreq;
	mreq = nullptr;
	rctp->Terminate();
	sendBytes = recvBytes = 0;
}

void RUDP::Update(uint32_t timeout)
{
	HandleRecv();
	rctp->Update(timeout);
}


void RUDP::SetProtocolFrame(uint64_t frame)
{
	rctp->SetProtocolFrame(frame);
}

uint64_t RUDP::GetProtocolFrame()
{
	return rctp->GetProtocolFrame();
}

void RUDP::ClearCache()
{
	rctp->Clear();
}

void RUDP::SendTo(uint8_t* buffer, uint32_t len, sockaddr_in* remote, bool reliable)
{
	UserData ud;
	memcpy(ud.data, remote, sizeof(sockaddr_in));
	rctp->Send(buffer, len, reliable && !isJoin, ud);
}

void RUDP::SendTo(uint8_t* buffer, uint32_t len, sockaddr_in* remote)
{
	sendto(udpSocket, (char*)buffer, len, 0, (sockaddr*)remote, sizeof(sockaddr));
	sendBytes += len;
}

void RUDP::SendTo(uint8_t* buffer, uint32_t len, const char* ip, const uint16_t port, bool reliable)
{
	sockaddr_in _add;
	sockaddr_in* addr = &_add;
	addr->sin_port = htons(port);
	addr->sin_addr.s_addr = inet_addr(ip);
	addr->sin_family = AF_INET;
	SendTo(buffer, len, addr, reliable);
}



void RUDP::SendToImp(uint8_t* buffer, uint32_t len, UserData ud)
{
	SendTo(buffer, len, (sockaddr_in*)ud.data);
}

bool RUDP::CompareAddr(sockaddr_in* addr1, sockaddr_in* addr2)
{
	if (addr1->sin_port == addr2->sin_port && addr1->sin_addr.s_addr == addr2->sin_addr.s_addr)
		return true;
	return false;
}

void RUDP::SetOption(int option, uint32_t value)
{
	rctp->SetOption(option, value);
}

void RUDP::SetOption(int option, float value)
{
	rctp->SetOption(option, value);
}

uint32_t RUDP::GetTimestamp()
{
	return RCTP::GetTimestamp();
}

bool RUDP::GetLocalIP(char** outIP)
{
	if (!udpSocket) return false;
	sockaddr_in name;
#if _WIN32
	int len = sizeof(sockaddr);
#else
	socklen_t len = sizeof(sockaddr);
#endif
	if (getsockname(udpSocket, (sockaddr*)&name, &len))
		return false;
	*outIP = inet_ntoa(*(in_addr*)(&name.sin_addr.s_addr));
	return true;
}

bool RUDP::GetLocalPort(int* outPort)
{
	if (!udpSocket) return false;
	sockaddr_in name;
#if _WIN32
	int len = sizeof(sockaddr);
#else
	socklen_t len = sizeof(sockaddr);
#endif
	if (getsockname(udpSocket, (sockaddr*)&name, &len))
		return false;
	*outPort = ntohs(name.sin_port);
	return true;
}

bool RUDP::JoinMulticastGroup(const char* group)
{
	if (!udpSocket) return false;
	if (isJoin) return false;
	mreq->imr_multiaddr.s_addr = inet_addr(group);
	if (setsockopt(udpSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)mreq, sizeof(ip_mreq)))
		return false;
	isJoin = true;
	return true;
}

void RUDP::DorpMulticastGroup()
{
	if (!udpSocket) return;
	if (!isJoin) return;
	if (setsockopt(udpSocket, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)mreq, sizeof(ip_mreq)))
		return;
	isJoin = false;
}

void RUDP::ConvertSockAddr(sockaddr_in* addr, const char* ip, uint32_t port)
{
	addr->sin_port = htons(port);
	addr->sin_addr.s_addr = inet_addr(ip);
	addr->sin_family = AF_INET;
}

void RUDP::OnSendImp(Message* msg, SendState state)
{
	if (onSend)
		onSend(msg, state, User);
}

void RUDP::OnRecvMsg(uint8_t* buffer, uint32_t len, bool reliable, UserData userdata)
{
	if (onRecv)
		onRecv(buffer, len, (sockaddr_in*)userdata.data, reliable, User);
}

void RUDP::HandleRecv()
{
	while (udpSocket)
	{
		UserData ud;
		sockaddr_in* addrClient = (sockaddr_in*)ud.data;

#if _WIN32
		int addrlen = sizeof(sockaddr_in);
		int len = recvfrom(udpSocket, buffer, BUFF_SIZE + HEAD_SIZE, 0, (sockaddr*)addrClient, &addrlen);
#else
		socklen_t addrlen = sizeof(sockaddr_in);
		int len = recvfrom(udpSocket, (char*)buffer, BUFF_SIZE + HEAD_SIZE, 0, (sockaddr*)addrClient, &addrlen);
#endif
		if (len <= 0)
			break;
		buffer[len] = 0;
		rctp->InputOriginRecv((uint8_t*)buffer, len, ud);
		recvBytes += len;
	}
}


void RUDP::ErrorLog(const char* _Format, ...)
{
	va_list ap;
	char buf[LOG_BUF_SIZE] = { 0 };
	va_start(ap, _Format);
	vsnprintf(buf, LOG_BUF_SIZE, _Format, ap);
	va_end(ap);

	if (onErrorLog)
		onErrorLog(buf, User);
	else
		printf("%s\n", buf);
}

